#include "env_map_demo.h"

#include <stdio.h>

#include "effects/reflection_effect.h"
#include "effects/refraction_effect.h"
#include "effects/fresnel_effect.h"
#include "ui/controllers/effect_ctrl.h"
#include "ui/controllers/refraction_effect_ctrl.h"
#include "ui/controllers/reflection_effect_ctrl.h"
#include "ui/controllers/fresnel_effect_ctrl.h"
#include "graphics/cube_texture_gl.h"
#include "graphics/cube_image.h"
#include "graphics/image_texture_gl.h"
#include "graphics/utils.h"
#include "graphics/render_device_fbo.h"
#include "graphics/render_buffer.h"
#include "graphics/error_gl.h"
#include "graphics/program_glsl.h"
#include "shapes/triangle_mesh.h"
#include "tools/obj_loader.h"
#include "common/gaussian.h"

namespace venus {
EnvMapDemo::EnvMapDemo() 
    : GlutWindow("Environment mapping demo", 10, 10, 800, 600, 
                 GLUT_RGBA|GLUT_DOUBLE),  
      camera_(Point(0, 0, 50)), color_(.1, .1, .72, 1.0) {
  camera_.set_fovy(45.0);
  camera_.set_min_dist(5.0);
  camera_.set_max_dist(450);
  camera_.set_z_far(1000.0);
  camera_.set_velocity(0, 0, 10);
  effect_ = NULL;
  effect_ctrl_ = NULL;
  cube_tex_ = NULL;
  hdr_cube_tex_ = NULL;
  mesh_ = NULL;
  hdr_target_ = NULL;
  bloom_target_ = NULL;
  Utils::SetNULL(blur_x_target_, s_blur_passes);
  Utils::SetNULL(blur_y_target_, s_blur_passes);
  depth_buffer_ = NULL;
  device_ = NULL;
  ratio_ = 0.5;

  bloom_effect_ = NULL;
  blur_x_effect_ = NULL;
  blur_y_effect_ = NULL;
  tone_effect_ = NULL;

  bright_threshold_ = 1.2f;
  bloom_factor_ = 0.5f;
  exposure_ = 1.1f;
  hdr_ = true;

  initialized_ = false;
  left_button_down_ = false;
  right_button_down_ = false;
}

EnvMapDemo::~EnvMapDemo() {
  this->ReleaseResources();
}

bool EnvMapDemo::Initialize(const char *model_file, const char *env_file) {
  GLenum err = glewInit();
  Info("VENDOR=%s", glGetString(GL_VENDOR));
  Info("RENDERER=%s", glGetString(GL_RENDERER));
  Info("VERSION=%s", glGetString(GL_VERSION));
  if (GLEW_OK != err) {
    Info("%s", glewGetErrorString(err));
    return false;
  }

  glClearDepth(1.f);
  glDepthFunc(GL_LEQUAL);
  glShadeModel(GL_SMOOTH);

  CubeImage image;
  if (!image.Load(env_file)) {
    Error("EnvMapDemo:Initialize, load env map failed");
    this->ReleaseResources();
    return false;
  }
  cube_tex_ = new CubeTextureGL;
  if (!cube_tex_->Initialize(image, GL_RGBA)) {
    Error("EnvMapDemo:Initialize, Intiailize env map failed");
    this->ReleaseResources();
    return false;
  }
  hdr_cube_tex_ = new CubeTextureGL;
  if (!hdr_cube_tex_->Initialize(image, GL_RGBA32F)) {
    Error("EnvMapDemo:Initialize, Intiailize hdr env map failed");
    this->ReleaseResources();
    return false;
  }
  mesh_ = LoadObj(model_file);
  if (NULL == mesh_) {
    Error("EnvMapDemo:Initialize, load model failed");
    this->ReleaseResources();
    return false;
  }
  this->LoadEffects();
  device_ = new RenderDeviceFBO;
  this->InitRenderTargets();
  Gaussian1D(s_gk, s_kRadius, 3.f);

  initialized_ = true;
  return true;
}

void EnvMapDemo::OnDisplay() {
  CheckErrorsGL("EnvMapDemo::OnDisplay");
  if (!initialized_) return;
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  camera_.ApplyViewTransform();
  if (hdr_) RenderHdr();
  else RenderRgb();

  DisplayStastics("Frame rate", fps_counter_.Update(), "fps");
  glutSwapBuffers();
}

void EnvMapDemo::RenderHdr() {
  device_->SaveMVP();
  device_->Activate();
  device_->SetRenderTarget(hdr_target_);
  device_->SetDepthBuffer(depth_buffer_);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  RenderSkybox(hdr_cube_tex_);
  RenderModel(hdr_cube_tex_);
  device_->DisableDepthBuffer();
  /* the enable is necessary, as the texture coordinates are to be specified
   * by glTexCoord2f. RenderModel doesn't need to do the enable. 
   * I think it's because it generate tex coordinates in shader
   */
  glEnable(GL_TEXTURE_2D);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  //the zNear should be less than zero, otherwise the fps won't show
  //disable depth test can also make the fps show, 
  //glOrtho(0, 1, 0, 1, -1, 1);
  gluOrtho2D(0, 1, 0, 1);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
 
  this->RenderBloom();
  device_->Deactivate();

  glActiveTexture(GL_TEXTURE0);
  hdr_target_->Bind();
  glActiveTexture(GL_TEXTURE1);
  bloom_target_->Bind();
  tone_effect_->Begin();
  GLint bright_threshold_var = tone_effect_->GetUniformVar("bright_threshold");
  GLint bloom_factor_var = tone_effect_->GetUniformVar("bloom_factor");
  GLint exposure_var = tone_effect_->GetUniformVar("exposure");
  GLint hdr_tex_var = tone_effect_->GetUniformVar("tex");
  GLint bloom_tex_var = tone_effect_->GetUniformVar("bloom");
  glUniform1i(hdr_tex_var, 0);
  glUniform1i(bloom_tex_var, 1);
  glUniform1f(exposure_var, exposure_);
  glUniform1f(bloom_factor_var, bloom_factor_);
  glUniform1f(bright_threshold_var, bright_threshold_);
  DrawQuad(0, 0, 1, 1);
  tone_effect_->End();

  glActiveTexture(GL_TEXTURE0);
  hdr_target_->Unbind();
  glActiveTexture(GL_TEXTURE1);
  bloom_target_->Unbind();
  device_->RestoreMVP();
}

void EnvMapDemo::RenderRgb() {
  RenderSkybox(cube_tex_);
  RenderModel(cube_tex_);
}

void EnvMapDemo::RenderModel(const CubeTextureGL *env_map) {
  Info("EnvMapDemo::RenderModel");
  glActiveTexture(GL_TEXTURE1);
  env_map->Bind();
  effect_->Begin();
  CheckErrorsGL("EnvMapDemo::RenderModel1");
  glEnable(GL_CULL_FACE);
  glPushMatrix();
  const float s = 4.0f;
  glScalef(2, 2, 2);
  //glutSolidCube(s);
  //glutSolidTeapot(s);
  //glutSolidSphere(s, 32, 32);
  glEnable(GL_DEPTH_TEST);
  DrawTriangleMesh(mesh_);
  glDisable(GL_DEPTH_TEST);

  glPopMatrix();
  glDisable(GL_CULL_FACE);
  effect_->End();
  env_map->Unbind();
  glActiveTexture(GL_TEXTURE0);
  CheckErrorsGL("EnvMapDemo::RenderModel");
}

void EnvMapDemo::RenderBloom() {
  device_->SetRenderTarget(bloom_target_);
  bloom_effect_->Begin();
  GLint hdr_tex_var = bloom_effect_->GetUniformVar("tex");
  GLint bright_threshold_var = bloom_effect_->GetUniformVar("bright_threshold");
  glUniform1i(hdr_tex_var, 0);
  glUniform1f(bright_threshold_var, bright_threshold_);
  hdr_target_->Bind();
  DrawQuad(0, 0, 1, 1);

  ImageTextureGL *prev = bloom_target_;
  for (int i = 0; i < s_blur_passes; ++i) {
    device_->SetRenderTarget(blur_x_target_[i]);
    device_->AdjustViewport(blur_x_target_[i]);
    CheckErrorsGL("blur");
    blur_x_effect_->Begin();
    prev->Bind();
    GLint x_tex_var = blur_x_effect_->GetUniformVar("tex");
    GLint x_step_var = blur_x_effect_->GetUniformVar("step");
    GLint x_weight_var = blur_x_effect_->GetUniformVar("weight");
    float x_step = 1.f / blur_x_target_[i]->width();
    glUniform1i(x_tex_var, 0);
    glUniform1f(x_step_var, x_step);
    glUniform1fv(x_weight_var, s_kRadius, s_gk);
    DrawQuad(0, 0, 1, 1);

    device_->SetRenderTarget(blur_y_target_[i]);
    blur_y_effect_->Begin();
    blur_x_target_[i]->Bind();
    GLint y_tex_var = blur_y_effect_->GetUniformVar("tex");
    GLint y_step_var = blur_y_effect_->GetUniformVar("step");
    GLint y_weight_var = blur_y_effect_->GetUniformVar("weight");
    float y_step = 1.f / blur_y_target_[i]->height();
    glUniform1i(y_tex_var, 0);
    glUniform1f(y_step_var, y_step);
    glUniform1fv(y_weight_var, s_kRadius, s_gk);
    DrawQuad(0, 0, 1, 1);

    ProgramGLSL::Disable();
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    device_->SetRenderTarget(bloom_target_);
    device_->AdjustViewport(bloom_target_);
    blur_y_target_[i]->Bind();
    DrawQuad(0, 0, 1, 1);
    glDisable(GL_BLEND);

    prev = blur_y_target_[i];
  }
}

void EnvMapDemo::RenderSkybox(const CubeTextureGL *env_map) {
  Info("EnvMapDemo::RenderSkybox");
  GLfloat s_plane[] = { 1.0, 0.0, 0.0, 0.0 };
  GLfloat t_plane[] = { 0.0, 1.0, 0.0, 0.0 };
  GLfloat r_plane[] = { 0.0, 0.0, 1.0, 0.0 };
  glTexGenfv(GL_S, GL_OBJECT_PLANE, s_plane);
  glTexGenfv(GL_T, GL_OBJECT_PLANE, t_plane);
  glTexGenfv(GL_R, GL_OBJECT_PLANE, r_plane);

  glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
  glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
  glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);

  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

  glEnable(GL_TEXTURE_CUBE_MAP);
  glEnable(GL_TEXTURE_GEN_S);
  glEnable(GL_TEXTURE_GEN_T);
  glEnable(GL_TEXTURE_GEN_R);
  env_map->Bind();

  const float s = 500.0f;
  //glEnable(GL_CULL_FACE);
  //glCullFace(GL_FRONT);
  glutSolidCube(s+s);
  //glDisable(GL_CULL_FACE);
  env_map->Unbind();

  glDisable(GL_TEXTURE_GEN_S);
  glDisable(GL_TEXTURE_GEN_T);
  glDisable(GL_TEXTURE_GEN_R);
  glDisable(GL_TEXTURE_CUBE_MAP);
  CheckErrorsGL("EnvMapDemo::RenderSkybox");
}

void EnvMapDemo::OnResize(int width, int height) {
  if (this->width() != width || this->height() != height) {
    this->set_width(width); this->set_height(height);
    this->DeleteRenderTargets();
    this->InitRenderTargets();
  }
  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  camera_.set_aspect_ratio(width/static_cast<float>(height));
  camera_.ApplyProjectionTransform();
}

void EnvMapDemo::OnKeyboard(unsigned char key, int x, int y) {
  if (!initialized_) return;
  if (effect_ctrl_->OnKeyboard(key, x, y)) {
    ReDraw(); return;
  }
  switch (key) {
    case 'l':
      effect_ctrl_ = effect_ctrls_["reflection"];
      effect_ = effect_ctrl_->effect();
      break;
    case 'r':
      effect_ctrl_ = effect_ctrls_["refraction"];
      effect_ = effect_ctrl_->effect();
      break;
    case 'f':
      effect_ctrl_ = effect_ctrls_["fresnel"];
      effect_ = effect_ctrl_->effect();
      break;
    case 'a':
      camera_.Yaw(-1);
      break;
    case 'd':
      camera_.Yaw(1);
      break;
    case 'w':
      camera_.Walk(1);
      break;
    case 's':
      camera_.Walk(-1);
      break;
    case 'q':
      camera_.Pitch(1);
      break;
    case 'z':
      camera_.Pitch(-1);
      break;
    case 'h':
      hdr_ = !hdr_;
      break;
    default:
      return;
  }
  ReDraw();
}

void EnvMapDemo::OnMouse(int button, int state, int x, int y) {
  if (GLUT_LEFT_BUTTON == button) {
    if (GLUT_DOWN == state) { 
      left_button_down_ = true;
    } else {
      left_button_down_ = false;
    }
  } else if(GLUT_RIGHT_BUTTON == button) {
    if (GLUT_DOWN == state) {
      right_button_down_ = true;
    } else {
      right_button_down_ = false;
    }
  }
  last_pos_.x = x; last_pos_.y = y;
}

void EnvMapDemo::OnMouseMove(int x, int y) {
  static float step = 0.1;
  if (left_button_down_) {
    camera_.Yaw((last_pos_.x - x) * step);
    camera_.Pitch((last_pos_.y - y) * step);
    this->ReDraw();
  }
  if (right_button_down_) {
    camera_.Walk((y - last_pos_.y) * step);
    this->ReDraw();
  }
  last_pos_.x = x; last_pos_.y = y;
}

void EnvMapDemo::OnSpecialKey(int key, int x, int y) {
  if (effect_ctrl_->OnSpecialKey(key, x, y)) {
    ReDraw();
    return;
  }
}

void EnvMapDemo::LoadEffects() {
  ReflectionEffect *rfl_effect = new ReflectionEffect(0.2); 
  rfl_effect->set_env_map(1);
  rfl_effect->set_material_color(&color_);
  rfl_effect->set_eye_pos(&camera_.position());
  effect_ctrls_["reflection"] =  new ReflectionEffectCtrlGlut(
      rfl_effect, ScalarCtrlTriple(0, 5, 0.1));

  RefractionEffect *rfr_effect = new RefractionEffect(0.8);
  rfr_effect->set_env_map(1);
  rfr_effect->set_eye_pos(&camera_.position());
  effect_ctrls_["refraction"] =  new RefractionEffectCtrlGlut(
      rfr_effect, ScalarCtrlTriple(0, 1, .05));

  FresnelEffect *fresnel_effect = new FresnelEffect(0.8, 0.2, 1, 1);
  fresnel_effect->set_env_map(1);
  fresnel_effect->set_eye_pos(&camera_.position());
  effect_ctrls_["fresnel"] = new FresnelEffectCtrlGlut(
      fresnel_effect, ScalarCtrlTriple(0, 1, 0.05), ScalarCtrlTriple(0, 5, 0.1),
      ScalarCtrlTriple(0, 5, 0.1), ScalarCtrlTriple(0, 5, 0.1));

  effect_ctrl_ = effect_ctrls_["reflection"];
  effect_ = effect_ctrl_->effect();

  bloom_effect_ = new ProgramGLSL;
  bloom_effect_->Load(NULL, "shaders/extract_bloom.fp");
  bloom_effect_->Link();
  blur_x_effect_ = new ProgramGLSL;
  blur_x_effect_->Load(NULL, "shaders/bx.fp");
  blur_x_effect_->Link();
  blur_y_effect_ = new ProgramGLSL;
  blur_y_effect_->Load(NULL, "shaders/by.fp");
  blur_y_effect_->Link();
  tone_effect_ = new ProgramGLSL;
  tone_effect_->Load(NULL, "shaders/tone.fp");
  tone_effect_->Link();
}

void EnvMapDemo::DestroyEffects() {
  for (EffectCtrlMap::iterator it = effect_ctrls_.begin(); 
      effect_ctrls_.end() != it; ++it) {
    delete it->second;
  }
  delete bloom_effect_;
  delete blur_x_effect_;
  delete blur_y_effect_;
  delete tone_effect_;
}

void EnvMapDemo::DeleteRenderTargets() {
  Utils::Delete(depth_buffer_);
  Utils::Delete(hdr_target_);
  Utils::Delete(bloom_target_);
  Utils::Delete(blur_x_target_, s_blur_passes);
  Utils::Delete(blur_y_target_, s_blur_passes);
}

void EnvMapDemo::InitRenderTargets() {
  int width = this->width(), height = this->height();
  depth_buffer_ = new RenderBuffer(width, height, GL_DEPTH_COMPONENT);
  hdr_target_ = new ImageTextureGL();
  hdr_target_->Initialize(width, height, GL_RGBA16F_ARB);
  bloom_target_ = new ImageTextureGL();
  bloom_target_->Initialize(width, height, GL_RGBA16F_ARB);

  for (int i = 0; i < s_blur_passes; ++i) {
    blur_x_target_[i] = new ImageTextureGL();
    blur_x_target_[i]->Initialize(width, height, GL_RGBA16F_ARB);
    blur_y_target_[i] = new ImageTextureGL();
    blur_y_target_[i]->Initialize(width, height, GL_RGBA16F_ARB);
    width >>= 1; height >>= 1;
  }
}
void EnvMapDemo::ReleaseResources() {
  delete device_;
  delete cube_tex_;
  delete hdr_cube_tex_;
  delete mesh_;
  this->DestroyEffects();
  this->DeleteRenderTargets();
}

void EnvMapDemo::OnIdle() { this->ReDraw(); }

} // namespace venus
